CDK TypeScriptを用いてAPI GatewayでIP制限する方法

CDK TypeScriptを用いてAPI GatewayでIP制限する方法

Clock Icon2024.08.15

リテールアプリ共創部のるおんです。最近業務でCDKを用いてサーバーレス環境の構築をする機会がありました。その際にAPI Gatewayで送信元IP制限を実装したので、今回はその方法を共有したいと思います。
使用した言語はTypeScriptです。

API Gatewayとは

API Gateway は、AWS が提供するフルマネージドサービスで、開発者がAPIの作成、公開、維持、モニタリング、保護を簡単に行えるようにするものです。RESTful APIやWebSocket APIを作成でき、バックエンドサービス(Lambda、EC2、その他のAWSサービスなど)と連携することができます。

API Gatewayのリソースポリシーに、特定のIPからしかアクセスを許可しないことで、IP制限機能を実現したいと思います。

全体像

今回は以下の構成をAWS CDKを用いてデプロイします。

簡単なLambda関数を用意し、API Gatewayに特定のIPアドレスからのアクセスのみを許可する設定を行い、アクセス制御ができているかどうかを確認してみたいと思います。

スクリーンショット 2024-08-15 10.08.09

CDKではなく、マネジメントコンソールから実装する場合、以下の記事が参考になりました。
https://dev.classmethod.jp/articles/api-gateway-resouce-policy-ip-address/

CDK書いてみる

それではこの構成をCDKで実装していきます。
TypeScriptが使用可能で、AWS CDKを用いて手元のマシンからデプロイができる環境が整っていることを前提に進めていきます。

1.プロジェクトのセットアップ

まず、プロジェクトを作成してCDKを開始します。

bash
mkdir api-gateway-ip-restriction
cd api-gateway-ip-restriction
cdk init app --language typescript

IP制限をするためのリソースポリシー

セットアップができたら、IP制限するためのリソースポリシーテンプレートの書き方を確認してみます。
マネジメントコンソールからIP制限のテンプレートを確認することができるので、これを参考に、CDKを用いてポリシーを設定をします。
スクリーンショット 2024-08-14 11.56.15

コードで書くと以下の通りです。

typescript
    /**
     * API Gatewayで使用するIP制限リソースポリシー
     */
    const resourcePolicy = new aws_iam.PolicyDocument({
      statements: [
        new aws_iam.PolicyStatement({
          effect: aws_iam.Effect.ALLOW,
          principals: [new aws_iam.AnyPrincipal()],
          actions: ["execute-api:Invoke"],
          resources: ["execute-api:/*/*/*"],
        }),
        new aws_iam.PolicyStatement({
          effect: aws_iam.Effect.DENY,
          principals: [new aws_iam.AnyPrincipal()],
          actions: ["execute-api:Invoke"],
          resources: ["execute-api:/*/*/*"],
          conditions: {
            NotIpAddress: {
              // ここで許可したい IP アドレスを指定する
              "aws:SourceIp": ["xxx.xxx.xxx.xx"],
            },
          },
        }),
      ],
    });

CDKのPolicyDocumentクラスと、PolicyStatementクラスを用いて、独自のポリシーを作成することができます。
ポリシーは2つの主要なステートメントで構成されています。
一つ目のステートメントは、ALLOWステートメントです。このステートメントは、すべてのプリンシパル(ユーザー)に対して、API Gatewayの全てのリソースに対するexecute-api:Invokeアクションを許可しています。
もう一方のステートメントはDENYステートメントです。このステートメントは、指定されたIPアドレス以外からのアクセスを拒否します。

これによって、以下の動作をします。

  1. まず、ALLOWステートメントですべてのアクセスを許可します。
  2. 次に、DENYステートメントで指定されたIP以外からのアクセスを拒否します。
  3. AWSのポリシー評価ロジックでは、明示的なDENYが他のすべての許可よりも優先されるため、結果として指定されたIPアドレスからのアクセスのみが許可されます。

これをAPI Gatewayに紐づけることにより、特定のIPアドレス以外からのアクセスを制限することが可能です。

typescript
/**
     * API Gatewayの作成
     */
    const api = new aws_apigateway.RestApi(
      this,
      "HelloApi",
      {
        // リソースポリシーを設定
        policy: resourcePolicy,
      },
    );

これを用いてLambda+API Gateway構成を、IP制限ありで実装してみます。

2. スタックコード全体

以下が完全なスタックコードです:
lib/api-gateway-ip-restriction-stack.tsに以下のコードを記述します。

lib/api-gateway-ip-restriction-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as aws_iam from "aws-cdk-lib/aws-iam";
import * as aws_apigateway from "aws-cdk-lib/aws-apigateway";

export class ApiGatewayIpRestrictionStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * API Gatewayで使用するLambda
     */
    const sampleLambda = new nodejs.NodejsFunction(this, "SampleLambda", {
      entry: "lambda/sample/handler.ts",
      runtime: lambda.Runtime.NODEJS_20_X,
      functionName: "SampleLambda",
      description: "サンプルのラムダ関数を作成",
      architecture: lambda.Architecture.ARM_64,
    });

    /**
     * API Gatewayで使用するIP制限リソースポリシー
     */
    const resourcePolicy = new aws_iam.PolicyDocument({
      statements: [
        new aws_iam.PolicyStatement({
          effect: aws_iam.Effect.ALLOW,
          principals: [new aws_iam.AnyPrincipal()],
          actions: ["execute-api:Invoke"],
          resources: ["execute-api:/*/*/*"],
        }),
        new aws_iam.PolicyStatement({
          effect: aws_iam.Effect.DENY,
          principals: [new aws_iam.AnyPrincipal()],
          actions: ["execute-api:Invoke"],
          resources: ["execute-api:/*/*/*"],
          conditions: {
            NotIpAddress: {
              // ここで許可したい IP アドレスを指定する
              "aws:SourceIp": ["xxx.xxx.xxx.xx"],
            },
          },
        }),
      ],
    });

    /**
     * API Gatewayの作成
     */
    const api = new aws_apigateway.RestApi(
      this,
      "HelloApi",
      {
        // リソースポリシーを設定
        policy: resourcePolicy,
      },
    );

    // LambdaとAPI Gatewayの統合
    const helloIntegration = new aws_apigateway.LambdaIntegration(sampleLambda);
    api.root.addMethod("GET", helloIntegration);
  }
}

簡単なレスポンスを返すだけのLambda関数をlambda/sample/handler.ts として実装しました。

lambda/sample/handler.ts
export const handler = async (event: any) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

動作確認

作成したスタックをデプロイして動作確認をしてみます。

bash
cdk deploy

指定したIPアドレスからAPI Gatewayのエンドポイントにブラウザからアクセスすると、以下のように無事呼び出すことができるはずです。
スクリーンショット 2024-08-14 12.30.40

次に、指定されたIPアドレス以外からアクセスを試みます。以下のような表示になり、API Gatewayにアクセスできないことが確認できていたら成功です!
スクリーンショット 2024-08-14 12.29.26

おわりに

今回、AWS CDK を使用してAPI GatewayにIP制限を実装する方法を紹介しました。この方法を使うことで、APIへのアクセスを特定のIPアドレスに制限し、セキュリティを向上させることができます。
ただし、IP 制限だけでなく、適切な認証・認可の仕組みも併せて実装することをお勧めします。また、IP アドレスは変更される可能性があるため、定期的に制限リストを見直すことも重要です。

参考になれば幸いです。

参考

Amazon API Gateway
公式リファレンス
API GatewayのリソースポリシーでAPIにアクセスできる送信元IPアドレスを制限する

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.